﻿#region Copyright and license information
// Copyright 2012 Richard Schneider
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

using NodaTime.Utility;
using System;
using System.Collections.Generic;
using System.Globalization;

namespace NodaTime.Calendars
{

    /// <summary>
    ///   Maps a <see cref="CalendarSystem"/> to a unique name and vice-versa.
    /// </summary>
    /// <remarks>
    ///   A unique name for a calendar is required when serialising a date/time that is relative to a <see cref="CalendarSystem"/>.
    ///   As of 2 Nov 2012 (ISO calendar) there are no ISO or RFC standards for naming a <see cref="CalendarSystem"/>, even the 
    ///   <see cref="CalendarSystem.Name">NodaTime.CalendarSystem.Name</see>, as documented,  is not unique.  
    /// <para>
    ///   Currently, <b>CalendarNames</b> is hardwired for the known Noda calendar systems.  Its one saving grace is that any classes 
    ///   requiring a unique name have a single point of failure.
    /// </para>
    /// <note>
    ///   Anyone adding a new <see cref="CalendarSystem"/> must modify this class.
    /// </note>
    /// <list type="table">
    ///   <listheader>
    ///     <term>Calendar name</term>
    ///     <description>Noda definition</description>
    ///   </listheader>
    /// <item><term>ISO</term><description><see cref="CalendarSystem.Iso"/></description></item>
    /// <item><term>Gregorian 1</term><description><see cref="CalendarSystem.GetGregorianCalendar"/>(1)</description></item>
    /// <item><term>Gregorian 2</term><description><see cref="CalendarSystem.GetGregorianCalendar"/>(2)</description></item>
    /// <item><term>Gregorian 3</term><description><see cref="CalendarSystem.GetGregorianCalendar"/>(3)</description></item>
    /// <item><term>Gregorian 5</term><description><see cref="CalendarSystem.GetGregorianCalendar"/>(5)</description></item>
    /// <item><term>Gregorian 6</term><description><see cref="CalendarSystem.GetGregorianCalendar"/>(6)</description></item>
    /// <item><term>Gregorian 7</term><description><see cref="CalendarSystem.GetGregorianCalendar"/>(7)</description></item>
    /// <item><term>Coptic 1</term><description><see cref="CalendarSystem.GetCopticCalendar"/>(1)</description></item>
    /// <item><term>Coptic 2</term><description><see cref="CalendarSystem.GetCopticCalendar"/>(2)</description></item>
    /// <item><term>Coptic 3</term><description><see cref="CalendarSystem.GetCopticCalendar"/>(3)</description></item>
    /// <item><term>Coptic 4</term><description><see cref="CalendarSystem.GetCopticCalendar"/>(4)</description></item>
    /// <item><term>Coptic 5</term><description><see cref="CalendarSystem.GetCopticCalendar"/>(5)</description></item>
    /// <item><term>Coptic 6</term><description><see cref="CalendarSystem.GetCopticCalendar"/>(6)</description></item>
    /// <item><term>Coptic 7</term><description><see cref="CalendarSystem.GetCopticCalendar"/>(7)</description></item>
    /// <item><term>Julian 1</term><description><see cref="CalendarSystem.GetJulianCalendar"/>(1)</description></item>
    /// <item><term>Julian 2</term><description><see cref="CalendarSystem.GetJulianCalendar"/>(2)</description></item>
    /// <item><term>Julian 3</term><description><see cref="CalendarSystem.GetJulianCalendar"/>(3)</description></item>
    /// <item><term>Julian 4</term><description><see cref="CalendarSystem.GetJulianCalendar"/>(4)</description></item>
    /// <item><term>Julian 5</term><description><see cref="CalendarSystem.GetJulianCalendar"/>(5)</description></item>
    /// <item><term>Julian 6</term><description><see cref="CalendarSystem.GetJulianCalendar"/>(6)</description></item>
    /// <item><term>Julian 7</term><description><see cref="CalendarSystem.GetJulianCalendar"/>(7)</description></item>
    /// <item><term>Hijri Civil-Indian</term><description><see cref="CalendarSystem.GetIslamicCalendar"/>(IslamicLeapYearPattern.Indian, IslamicEpoch.Civil)</description></item>
    /// <item><term>Hijri Civil-Base15</term><description><see cref="CalendarSystem.GetIslamicCalendar"/>(IslamicLeapYearPattern.Base15, IslamicEpoch.Civil)</description></item>
    /// <item><term>Hijri Civil-Base16</term><description><see cref="CalendarSystem.GetIslamicCalendar"/>(IslamicLeapYearPattern.Base16, IslamicEpoch.Civil)</description></item>
    /// <item><term>Hijri Civil-HabashAlHasib</term><description><see cref="CalendarSystem.GetIslamicCalendar"/>(IslamicLeapYearPattern.HabashAlHasib, IslamicEpoch.Civil)</description></item>
    /// <item><term>Hijri Astronomical-Indian</term><description><see cref="CalendarSystem.GetIslamicCalendar"/>(IslamicLeapYearPattern.Indian, IslamicEpoch.Astronomical)</description></item>
    /// <item><term>Hijri Astronomical-Base15</term><description><see cref="CalendarSystem.GetIslamicCalendar"/>(IslamicLeapYearPattern.Base15, IslamicEpoch.Astronomical)</description></item>
    /// <item><term>Hijri Astronomical-Base16</term><description><see cref="CalendarSystem.GetIslamicCalendar"/>(IslamicLeapYearPattern.Base16, IslamicEpoch.Astronomical)</description></item>
    /// <item><term>Hijri Astronomical-HabashAlHasib</term><description><see cref="CalendarSystem.GetIslamicCalendar"/>(IslamicLeapYearPattern.HabashAlHasib, IslamicEpoch.Astronomical)</description></item>
    /// </list>
    /// <note>
    ///   CalendarSystem.GetGregorianCalendar(4) is always named "ISO".
    /// </note>
    /// </remarks>
    public static class CalendarNames
    {
        static readonly Dictionary<string, Func<CalendarSystem>> Calendars;

        static CalendarNames()
        {
            Calendars = new Dictionary<string, Func<CalendarSystem>>
            {
                { "ISO", () => CalendarSystem.Iso },
                { "Gregorian 1", () => CalendarSystem.GetGregorianCalendar(1) },
                { "Gregorian 2", () => CalendarSystem.GetGregorianCalendar(2) },
                { "Gregorian 3", () => CalendarSystem.GetGregorianCalendar(3) },
                // This is the ISO calender. { "Gregorian 4", () => CalendarSystem.GetGregorianCalendar(4) }, 
                { "Gregorian 5", () => CalendarSystem.GetGregorianCalendar(5) },
                { "Gregorian 6", () => CalendarSystem.GetGregorianCalendar(6) },
                { "Gregorian 7", () => CalendarSystem.GetGregorianCalendar(7) },
                { "Coptic 1", () => CalendarSystem.GetCopticCalendar(1) },
                { "Coptic 2", () => CalendarSystem.GetCopticCalendar(2) },
                { "Coptic 3", () => CalendarSystem.GetCopticCalendar(3) },
                { "Coptic 4", () => CalendarSystem.GetCopticCalendar(4) },
                { "Coptic 5", () => CalendarSystem.GetCopticCalendar(5) },
                { "Coptic 6", () => CalendarSystem.GetCopticCalendar(6) },
                { "Coptic 7", () => CalendarSystem.GetCopticCalendar(7) },
                { "Julian 1", () => CalendarSystem.GetJulianCalendar(1) },
                { "Julian 2", () => CalendarSystem.GetJulianCalendar(2) },
                { "Julian 3", () => CalendarSystem.GetJulianCalendar(3) },
                { "Julian 4", () => CalendarSystem.GetJulianCalendar(4) },
                { "Julian 5", () => CalendarSystem.GetJulianCalendar(5) },
                { "Julian 6", () => CalendarSystem.GetJulianCalendar(6) },
                { "Julian 7", () => CalendarSystem.GetJulianCalendar(7) },
                { "Hijri Civil-Indian", () => CalendarSystem.GetIslamicCalendar(IslamicLeapYearPattern.Indian, IslamicEpoch.Civil) },
                { "Hijri Civil-Base15", () => CalendarSystem.GetIslamicCalendar(IslamicLeapYearPattern.Base15, IslamicEpoch.Civil) },
                { "Hijri Civil-Base16", () => CalendarSystem.GetIslamicCalendar(IslamicLeapYearPattern.Base16, IslamicEpoch.Civil) },
                { "Hijri Civil-HabashAlHasib", () => CalendarSystem.GetIslamicCalendar(IslamicLeapYearPattern.HabashAlHasib, IslamicEpoch.Civil) },
                { "Hijri Astronomical-Indian", () => CalendarSystem.GetIslamicCalendar(IslamicLeapYearPattern.Indian, IslamicEpoch.Astronomical) },
                { "Hijri Astronomical-Base15", () => CalendarSystem.GetIslamicCalendar(IslamicLeapYearPattern.Base15, IslamicEpoch.Astronomical) },
                { "Hijri Astronomical-Base16", () => CalendarSystem.GetIslamicCalendar(IslamicLeapYearPattern.Base16, IslamicEpoch.Astronomical) },
                { "Hijri Astronomical-HabashAlHasib", () => CalendarSystem.GetIslamicCalendar(IslamicLeapYearPattern.HabashAlHasib, IslamicEpoch.Astronomical) },
            };
        }

        /// <summary>
        ///   Maps the <see cref="CalendarSystem"/> to a unique name.
        /// </summary>
        /// <param name="calendar">A <see cref="CalendarSystem"/>.</param>
        /// <returns>A unique name for the <paramref name="calendar"/>.</returns>
        /// <exception cref="ArgumentNullException">
        ///   When <paramref name="calendar"/> is <b>null</b>.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        ///   When <paramref name="calendar"/> is not a known calendar.  This is an internal Noda error, the calendar system has not been defined in this class.
        /// </exception>
        /// <remarks>
        ///   <b>ToString</b> guarantees that the returned string can be supplied to <see cref="ToCalendarSystem"/>.  Any other assumption is wrong.
        /// </remarks>
        public static string ToString(CalendarSystem calendar)
        {
            Preconditions.CheckNotNull(calendar, "calendar");

            var systemName = calendar.SystemName;
            string name;
            switch (systemName)
            {
                case "ISO":
                    name = "ISO";
                    break;
                case "Coptic":
                case "Julian":
                    name = systemName + " " + ((BasicCalendarSystem)calendar).MinDaysInFirstWeek.ToString(CultureInfo.InvariantCulture);
                    break;
                case "Gregorian":
                    var gregorian = (GregorianCalendarSystem) calendar;
                    if (gregorian.MinDaysInFirstWeek == 4)
                        name = "ISO";
                    else
                        name = systemName + " " + gregorian.MinDaysInFirstWeek.ToString(CultureInfo.InvariantCulture);
                    break;
                case "Hijri (Islamic)":
                    var hijri = (IslamicCalendar) calendar;
                    name = "Hijri "
                        + hijri.Epoch.ToString() + "-"
                        + hijri.LeapYearPattern.ToString();
                    break;
                default:
                    throw new InvalidOperationException(string.Format(Properties.Messages.Calendar_UnknownCalendarSystem, systemName));
            }

            if (Calendars.ContainsKey(name))
                return name;

            throw new InvalidOperationException(string.Format(Properties.Messages.Calendar_UnknownCalendarSystem, name));
        }

        /// <summary>
        ///   Maps a calendar name to a <see cref="CalendarSystem"/>.
        /// </summary>
        /// <param name="name">
        ///   A unique name for a <see cref="CalendarSystem"/>.</param>
        /// <returns>A <see cref="CalendarSystem"/> that maps to <paramref name="name"/>.</returns>
        /// <exception cref="ArgumentNullException">
        ///   When <paramref name="name"/> is <b>null</b>.
        /// </exception>
        /// <exception cref="KeyNotFoundException">
        ///   When <paramref name="name"/> is not a known calendar.
        /// </exception>
        public static CalendarSystem ToCalendarSystem(string name)
        {
            Preconditions.CheckNotNull(name, "name");

            Func<CalendarSystem> calendar;
            if (Calendars.TryGetValue(name, out calendar))
                return calendar();

            throw new KeyNotFoundException(string.Format(Properties.Messages.Calendar_UnknownCalendarSystem, name));
        }

        /// <summary>
        ///   Returns the unique name of all known <see cref="CalendarSystem">calendar systems</see>.
        /// </summary>
        /// <returns>
        ///   An <see cref="IEnumerable{String}"/> sequence of known calendar systems' names.
        /// </returns>
        public static IEnumerable<string> AllNames()
        {
            return Calendars.Keys;
        }
    }
}
